#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/version.h>
#include <linux/netfilter_arp.h>
#include <net/ip.h>

#include "socs/comm/types.h"
#include "socs/comm/socs.h"
#include "socs/comm/constants.h"
#include "socs/comm/config.h"
#include "socs/mod/sap.h"
#include "socs/mod/neigh.h"
#include "socs/mod/netlink.h"
#include "socs/mod/lladdr_resolve.h"
#include "socs/mod/netdev_monitor.h"
#include "socs/mod/multicast.h"
#include "socs/mod/core.h"
#include "socs/mod/pool6.h"



MODULE_LICENSE("GPL");
MODULE_AUTHOR("REYZAR");
MODULE_ALIAS("socs");
static char *saIP4[SAPSIZE];
static int saIP4_size;
module_param_array(saIP4, charp, &saIP4_size, 0);
static char *saIP6[SAPSIZE];
static int saIP6_size;
module_param_array(saIP6, charp, &saIP6_size, 0);

static char *mcIP4;
module_param(mcIP4, charp, 0);

static char *mcIP6;
module_param(mcIP6, charp, 0);

static unsigned int tmStable = TMSTABLE_DEF;
module_param(tmStable, uint, 0);

static unsigned int tmDead = TMDEAD_DEF;
module_param(tmDead, uint, 0);

static unsigned int tmHello = TMHELLO_DEF;
module_param(tmHello, uint, 0);

static char *hostname;
module_param(hostname, charp, 0);

static char *csDomain;
module_param(csDomain, charp, 0);

#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 13, 0)
#define HOOK_ARG_TYPE const struct nf_hook_ops *
#else
#define HOOK_ARG_TYPE unsigned int
#endif


/*reply the arp request for sap and active the sap*/
static unsigned int arphook_rcv(HOOK_ARG_TYPE hook, struct sk_buff *skb,
								const struct net_device *in, const struct net_device *out,
								int (*okfn)(struct sk_buff *))
{
	return sap_arp_rcv(skb);
}
static unsigned int ndischook_rcv(HOOK_ARG_TYPE hook, struct sk_buff *skb,
								  const struct net_device *in, const struct net_device *out,
								  int (*okfn)(struct sk_buff *))
{
	return sap_ndisc_rcv(skb);
}
static struct nf_hook_ops lladdr_ops[] = {
	{
		.hook              = arphook_rcv,
		.hooknum           = NF_ARP_IN,
		.pf                = NFPROTO_ARP,
		.priority          = NF_PRI_ARP_SOCS_SAIP,
	},
	{
		.hook              = ndischook_rcv,
		.hooknum           = NF_INET_PRE_ROUTING,
		.pf                = NFPROTO_IPV6,
		.priority          = NF_PRI_IP_SOCS_FIRST,
	}

};

static unsigned int
hello_hook_v4(HOOK_ARG_TYPE hook, struct sk_buff *skb,
			  const struct net_device *in, const struct net_device *out,
			  int (*okfn)(struct sk_buff *))
{
	return hello_core4(skb);
}

static unsigned int
hello_hook_v6(HOOK_ARG_TYPE hook, struct sk_buff *skb,
			  const struct net_device *in, const struct net_device *out,
			  int (*okfn)(struct sk_buff *))
{
	return hello_core6(skb);
}

static unsigned int
hook_v4(HOOK_ARG_TYPE hook, struct sk_buff *skb,
		const struct net_device *in, const struct net_device *out,
		int (*okfn)(struct sk_buff *))
{
	//printk("******[%s]   protocol:%d   dst:%pI4   src:%pI4  dport:%d   sport:%d   pkt_type:%d   indev:%s ******\n",
		   //__FUNCTION__, ip_hdr(skb)->protocol,&ip_hdr(skb)->daddr, &ip_hdr(skb)->saddr, ntohs(tcp_hdr(skb)->dest),
		   //ntohs(tcp_hdr(skb)->source),skb->pkt_type,in->name);
	return core_v4(skb);
}

static unsigned int
hook_v6(HOOK_ARG_TYPE hook, struct sk_buff *skb,
		const struct net_device *in, const struct net_device *out,
		int (*okfn)(struct sk_buff *))
{
	//printk("******[%s], saddr[%pI6c] daddr[%pI6c] pkt_type:%d, dport:%d, indev:%s\n", __FUNCTION__,
		   //&ipv6_hdr(skb)->saddr, &ipv6_hdr(skb)->daddr,ntohs(tcp_hdr(skb)->dest), skb->pkt_type,in->name);
	return core_v6(skb);
}

static struct nf_hook_ops socs_ops[] __read_mostly = {
	{
		.hook           = hook_v4,
		.owner          = THIS_MODULE,
		.pf             = NFPROTO_IPV4,
		.hooknum        = NF_INET_PRE_ROUTING,
		.priority       = NF_PRI_IP_SOCS_FIRST,
	},
	{
		.hook           = hook_v6,
		.owner          = THIS_MODULE,
		.pf             = NFPROTO_IPV6,
		.hooknum        = NF_INET_PRE_ROUTING,
		.priority       = NF_PRI_IP_SOCS_FIRST,
	},
	{
		.hook           = hello_hook_v4,
		.owner          = THIS_MODULE,
		.pf             = NFPROTO_IPV4,
		.hooknum        = NF_INET_LOCAL_IN/*NF_INET_PRE_ROUTING*/,
		.priority       = NF_PRI_IP_SOCS_FIRST,
	},
	{
		.hook 			= hello_hook_v6,
		.owner 			= THIS_MODULE,
		.pf 			= NFPROTO_IPV6,
		.hooknum 		= NF_INET_LOCAL_IN,
		.priority       = NF_PRI_IP_SOCS_FIRST,
	},
};
static void deinit(void)
{
	socs_neigh_destroy();
	nl_kernel_destroy();
	netdev_monitor_destroy();
	multicast_destroy();
	saptb_destroy();
	config_destroy();
	pool6_destroy();
}

static int __init socs_init(void)
{
	int error;
	log_debug("Inserting the module:saIP4:%s,%s  saIP6:%s,%s  %s", saIP4[0],
			  saIP4[1], saIP6[0], saIP6[1], hostname);
	error = config_init(csDomain, hostname, tmHello, tmDead, tmStable, mcIP4,
						mcIP6);

	if (error) {
		goto failure;
	}

	error = socs_neigh_init();

	if (error) {
		goto failure;
	}

	error = saptb_init(saIP4, saIP4_size, saIP6, saIP6_size);

	if (error) {
		goto failure;
	}

	error = pool6_init();

	if (error) {
		goto failure;
	}

	error = multicast_init();

	if (error) {
		goto failure;
	}

	error = netdev_monitor_init();

	if (error) {
		goto failure;
	}

	error = nl_kernel_init();

	if (error) {
		goto failure;
	}

	error = nf_register_hooks(socs_ops, ARRAY_SIZE(socs_ops));

	if (error) {
		goto failure;
	}

	error = nf_register_hooks(lladdr_ops, ARRAY_SIZE(lladdr_ops));

	if (error) {
		goto failure;
	}

	log_info(MODULE_NAME " module inserted -- BuildTime: %s %s", __DATE__,
			 __TIME__);
	return error;
failure:
	deinit();
	nf_unregister_hooks(socs_ops, ARRAY_SIZE(socs_ops));
	return error;
}
static void __exit socs_exit(void)
{
	nf_unregister_hooks(lladdr_ops, ARRAY_SIZE(lladdr_ops));
	nf_unregister_hooks(socs_ops, ARRAY_SIZE(socs_ops));
	deinit();
	log_info(MODULE_NAME " module removed");
}

module_init(socs_init);
module_exit(socs_exit);
